﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace UI.Dendrogram
{
    public class Branch : Canvas
    {
        #region events
        public event OnBranchUnSelected BranchUnSelected;
        public event OnBranchSelected BranchSelected;
        public event OnBranchExpanded BranchExpanded;
        public event OnBranchCollapsed BranchCollapsed;
        #endregion

        #region data
        public int ID { get; set; }
        public string Label { get; set; }
        public BinaryHierarchicalData Data { get; set; }
        #endregion

        #region settings
        private const double BallRadius = 6;
        public Orientation Orientation { get; set; }
        public double BranchThickness { get; set; }
        public Color BranchColor { get; set; }
        private bool _Collapsed;
        public bool Collapsed
        {
            get
            {
                return _Collapsed;
            }
            set
            {
                if (this._Collapsed != value)
                {
                    this._Collapsed = value;
                    this.Path.Data = this.DrawBranch();

                    if(this._LeftLeafText!=null)
                    {
                        if(this._Collapsed)
                            this._LeftLeafText.Visibility = Visibility.Collapsed;
                        else
                            this._LeftLeafText.Visibility = Visibility.Visible;
                    }
                    if (this._RightLeafText != null)
                    {
                        if (this._Collapsed)
                            this._RightLeafText.Visibility = Visibility.Collapsed;
                        else
                            this._RightLeafText.Visibility = Visibility.Visible;
                    }
                }
            }
        }
        private bool _Selected;
        public bool Selected
        {
            get
            {
                return _Selected;
            }
            set
            {
                if (this._Selected != value)
                {
                    this._Selected = value;
                    if (this._Selected)
                    {
                        this.Path.Stroke = new SolidColorBrush(Colors.Red);
                    }
                    else
                    {
                        this.Path.Stroke = new SolidColorBrush(this.BranchColor);
                    }
                }
            }
        }
        #endregion

        #region UI
        public Point StartPos { get; set; }
        public Point HeaderPos { get; set; }
        public Point LeftShoulderEnd { get; set; }
        public Point RightShoulderEnd { get; set; }
        public Point LeftArmEnd { get; set; }
        public Point RightArmEnd { get; set; }
        public Rectangle BoundingBox { get; set; }
        public Path Path { get; set; }
        private readonly TextBlock _TooltipBlock;
        private readonly TextBlock _LeftLeafText;
        private readonly TextBlock _LeftLeafTooltip;
        private readonly TextBlock _RightLeafText;
        private readonly TextBlock _RightLeafTooltip;
        #endregion

        #region ctor
        public Branch(
            BinaryHierarchicalData data, Orientation orientation, Point startPoint,
            double leftShoulderLen, double rightShoulderLen, 
            double neckLength, double armLength)
        {
            this.Data = data;
            this.ID = data.ID;
            this.Label = data.Label;
            this.Orientation = orientation;
            this.StartPos = startPoint;
            this.BranchThickness = 2;
            this.BranchColor = Colors.Black;
            this.Collapsed = false;
            this.BoundingBox = new Rectangle();
            if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                this.HeaderPos = new Point() { X = startPoint.X + neckLength, Y = startPoint.Y };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y - leftShoulderLen };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X + armLength, Y = this.LeftShoulderEnd.Y };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y + rightShoulderLen };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X + armLength, Y = this.RightShoulderEnd.Y };
                this.BoundingBox.SetValue(Canvas.LeftProperty, startPoint.X);
                this.BoundingBox.SetValue(Canvas.TopProperty, startPoint.Y - leftShoulderLen);
                this.BoundingBox.Width = neckLength;
                this.BoundingBox.Height = leftShoulderLen+rightShoulderLen;
            }
            else
            {
                this.HeaderPos = new Point() { X = startPoint.X, Y = startPoint.Y + neckLength };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X - leftShoulderLen, Y = this.HeaderPos.Y };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X, Y = this.LeftShoulderEnd.Y + armLength };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X + rightShoulderLen, Y = this.HeaderPos.Y };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X, Y = this.RightShoulderEnd.Y + armLength };
                this.BoundingBox.SetValue(Canvas.LeftProperty, startPoint.X - leftShoulderLen);
                this.BoundingBox.SetValue(Canvas.TopProperty, startPoint.Y);
                this.BoundingBox.Width = leftShoulderLen+rightShoulderLen;
                this.BoundingBox.Height = neckLength;
            }
            this.BoundingBox.Fill = new SolidColorBrush(Colors.White);
            this.Children.Add(this.BoundingBox);

            this.Path = new Path();
            this.Path.Data = this.DrawBranch();
            this.Children.Add(this.Path);
            this.Path.StrokeThickness = this.BranchThickness;
            this.Path.Stroke = new SolidColorBrush(this.BranchColor);
            this.Path.Fill = new SolidColorBrush(Colors.White);

            this._TooltipBlock=new TextBlock();
            this._TooltipBlock.Text = this.Label;
            ToolTipService.SetToolTip(this.Path, this._TooltipBlock);

            if(this.Data.LeftLeaf !=null && this.Data.LeftChild==null)
            {
                this._LeftLeafText = new TextBlock();
                this.Children.Add(this._LeftLeafText);
                this._LeftLeafText.Text = this.Data.LeftLeaf.Label;
                this._LeftLeafText.SetValue(Canvas.LeftProperty,this.LeftArmEnd.X+5);
                this._LeftLeafText.SetValue(Canvas.TopProperty,this.LeftArmEnd.Y);
                if(Orientation==System.Windows.Controls.Orientation.Vertical)
                {
                    TransformGroup transformGroup = new TransformGroup();
                    RotateTransform rotateTransform = new RotateTransform();
                    rotateTransform.Angle = -90;
                    transformGroup.Children.Add(rotateTransform);
                    TranslateTransform translateTransform = new TranslateTransform();
                    translateTransform.X -= this._LeftLeafText.ActualHeight / 2;
                    translateTransform.Y += this._LeftLeafText.ActualWidth;
                    transformGroup.Children.Add(translateTransform);
                    this._LeftLeafText.RenderTransform = transformGroup;
                }
                else
                {
                    TranslateTransform translateTransform=new TranslateTransform();
                    translateTransform.Y -= this._LeftLeafText.ActualHeight / 2;
                    this._LeftLeafText.RenderTransform = translateTransform;
                }
                _LeftLeafTooltip = new TextBlock();
                _LeftLeafTooltip.Text = Data.LeftLeaf.TooltipText;
                ToolTipService.SetToolTip(_LeftLeafText,_LeftLeafTooltip);
            }
            if(this.Data.RightLeaf!=null && this.Data.RightChild==null)
            {
                this._RightLeafText=new TextBlock();
                this.Children.Add(this._RightLeafText);
                this._RightLeafText.Text = this.Data.RightLeaf.Label;
                this._RightLeafText.SetValue(Canvas.LeftProperty,this.RightArmEnd.X+5);
                this._RightLeafText.SetValue(Canvas.TopProperty,this.RightArmEnd.Y);
                if (this.Orientation == System.Windows.Controls.Orientation.Vertical)
                {
                    TransformGroup transformGroup=new TransformGroup();
                    RotateTransform rotateTransform = new RotateTransform();
                    rotateTransform.Angle = -90;
                    transformGroup.Children.Add(rotateTransform);
                    TranslateTransform translateTransform=new TranslateTransform();
                    translateTransform.X -= this._RightLeafText.ActualHeight/2;
                    translateTransform.Y += this._RightLeafText.ActualWidth;
                    transformGroup.Children.Add(translateTransform);
                    this._RightLeafText.RenderTransform = transformGroup;
                }
                else
                {
                    TranslateTransform translateTransform = new TranslateTransform();
                    translateTransform.Y -= this._RightLeafText.ActualHeight / 2;
                    this._RightLeafText.RenderTransform = translateTransform;
                }
                this._RightLeafTooltip = new TextBlock();
                this._RightLeafTooltip.Text = Data.RightLeaf.TooltipText;
                ToolTipService.SetToolTip(this._RightLeafText, this._RightLeafTooltip);
            }

            this.BoundingBox.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
            this.BoundingBox.MouseEnter += new MouseEventHandler(OnMouseEnter);
            this.BoundingBox.MouseLeave += new MouseEventHandler(OnMouseLeave);
            this.Path.MouseLeftButtonDown += new MouseButtonEventHandler(OnMouseLeftButtonDown);
            this.Path.MouseEnter += new MouseEventHandler(OnMouseEnter);
            this.Path.MouseLeave += new MouseEventHandler(OnMouseLeave);
        }

        void OnMouseLeave(object sender, MouseEventArgs e)
        {
            if (this.BranchUnSelected != null)
                this.BranchUnSelected(this);
        }

        void OnMouseEnter(object sender, MouseEventArgs e)
        {
            if (this.BranchSelected != null)
                this.BranchSelected(this);
        }

        void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.Collapsed)
            {
                if (this.BranchExpanded != null)
                    this.BranchExpanded(this);
            }
            else
            {
                if (this.BranchCollapsed != null)
                    this.BranchCollapsed(this);
            }
        }

        #endregion

        #region branch path
        private GeometryGroup DrawBranch()
        {
            GeometryGroup group = new GeometryGroup();
            // neck
            if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                group.Children.Add(
                    new LineGeometry()
                        {
                            StartPoint = this.StartPos,
                            EndPoint = new Point(){X=this.HeaderPos.X-BallRadius,Y=this.HeaderPos.Y}
                        });
            }
            else
            {
                group.Children.Add(
                    new LineGeometry()
                    {
                        StartPoint = this.StartPos,
                        EndPoint = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y - BallRadius }
                    });
            }
            if (!this.Collapsed)
            {
                if(this.Orientation==System.Windows.Controls.Orientation.Horizontal)
                {
                    // left shoulder
                    group.Children.Add(
                        new LineGeometry()
                            {
                                StartPoint = new Point() {X = this.HeaderPos.X, Y = this.HeaderPos.Y - BallRadius},
                                EndPoint = this.LeftShoulderEnd
                            });
                    // right shoulder
                    group.Children.Add(
                        new LineGeometry()
                            {
                                StartPoint = new Point() {X=this.HeaderPos.X,Y=this.HeaderPos.Y+BallRadius},
                                EndPoint = this.RightShoulderEnd
                            });
                }
                else
                {
                    // left shoulder
                    group.Children.Add(
                        new LineGeometry()
                        {
                            StartPoint = new Point() { X = this.HeaderPos.X-BallRadius, Y = this.HeaderPos.Y },
                            EndPoint = this.LeftShoulderEnd
                        });
                    // right shoulder
                    group.Children.Add(
                        new LineGeometry()
                        {
                            StartPoint = new Point() { X = this.HeaderPos.X+BallRadius, Y = this.HeaderPos.Y },
                            EndPoint = this.RightShoulderEnd
                        });
                }
                // left arm
                group.Children.Add(new LineGeometry() { StartPoint = this.LeftShoulderEnd, EndPoint = this.LeftArmEnd });
                // right arm
                group.Children.Add(new LineGeometry() { StartPoint = this.RightShoulderEnd, EndPoint = this.RightArmEnd });
            }

            // circle
            EllipseGeometry openCircle = new EllipseGeometry();
            openCircle.Center = new Point(this.HeaderPos.X, this.HeaderPos.Y);
            openCircle.RadiusX = BallRadius;
            openCircle.RadiusY = BallRadius;
            group.Children.Add(openCircle);
            // hline within circle
            LineGeometry hLine =
                   new LineGeometry()
                   {
                       StartPoint = new Point(this.HeaderPos.X - 3, this.HeaderPos.Y),
                       EndPoint = new Point(this.HeaderPos.X + 3, this.HeaderPos.Y)
                   };
            group.Children.Add(hLine);
            // vline within circle
            if (this.Collapsed)
            {
                LineGeometry vLine =
                       new LineGeometry()
                       {
                           StartPoint = new Point(this.HeaderPos.X, this.HeaderPos.Y - 3),
                           EndPoint = new Point(this.HeaderPos.X, this.HeaderPos.Y + 3)
                       };
                group.Children.Add(vLine);
            }
            group.FillRule = FillRule.EvenOdd;

            return group;
        }
        #endregion

        #region layout
        public void UpdateLayout(Point startPoint)
        {
            double neckLength = Math.Max(this.HeaderPos.X - this.StartPos.X, this.HeaderPos.Y - this.StartPos.Y);
            double leftShoulderLen = Math.Max(
                this.HeaderPos.Y - this.LeftShoulderEnd.Y, this.HeaderPos.X - this.LeftShoulderEnd.X);
            double rightShoulderLen = Math.Max(
                this.RightShoulderEnd.Y - this.HeaderPos.Y, this.RightShoulderEnd.X - this.HeaderPos.X);
            double leftArmLen = Math.Max(
                this.LeftArmEnd.X - this.LeftShoulderEnd.X, this.LeftArmEnd.Y - this.LeftShoulderEnd.Y);
            double rightArmLen = Math.Max(
                this.RightArmEnd.X - this.RightShoulderEnd.X, this.RightArmEnd.Y - this.RightShoulderEnd.Y);
            
            this.StartPos = startPoint;
            if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                this.HeaderPos = new Point() { X = startPoint.X + neckLength, Y = startPoint.Y };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y - leftShoulderLen };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X + leftArmLen, Y = this.LeftShoulderEnd.Y };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y + rightShoulderLen };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X + rightArmLen, Y = this.RightShoulderEnd.Y };
                this.BoundingBox.SetValue(Canvas.LeftProperty, startPoint.X);
                this.BoundingBox.SetValue(Canvas.TopProperty, startPoint.Y - leftShoulderLen);
                this.BoundingBox.Width = neckLength;
                this.BoundingBox.Height = leftShoulderLen + rightShoulderLen;
            }
            else
            {
                this.HeaderPos = new Point() { X = startPoint.X, Y = startPoint.Y + neckLength };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X - leftShoulderLen, Y = this.HeaderPos.Y };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X, Y = this.LeftShoulderEnd.Y + leftArmLen };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X + rightShoulderLen, Y = this.HeaderPos.Y };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X, Y = this.RightShoulderEnd.Y + rightArmLen };
                this.BoundingBox.SetValue(Canvas.LeftProperty, startPoint.X - leftShoulderLen);
                this.BoundingBox.SetValue(Canvas.TopProperty, startPoint.Y);
                this.BoundingBox.Width = leftShoulderLen + rightShoulderLen;
                this.BoundingBox.Height = neckLength;
            }

            this.Path.Data = this.DrawBranch();

            if (this.Data.LeftLeaf != null && this.Data.LeftChild == null)
            {
                this._LeftLeafText.SetValue(Canvas.LeftProperty, this.LeftArmEnd.X + 5);
                this._LeftLeafText.SetValue(Canvas.TopProperty, this.LeftArmEnd.Y);
            }
            if (this.Data.RightLeaf != null && this.Data.RightChild == null)
            {
                this._RightLeafText.SetValue(Canvas.LeftProperty, this.RightArmEnd.X + 5);
                this._RightLeafText.SetValue(Canvas.TopProperty, this.RightArmEnd.Y);
            }
        }

        public Point UpdateLayout(Point leftArmPos, Point rightArmPos, bool leftIsLeaf, bool rightIsLeaf)
        {
            double neckLength = Math.Max(this.HeaderPos.X - this.StartPos.X, this.HeaderPos.Y - this.StartPos.Y);
            double leftArmLen = Math.Max(
                this.LeftArmEnd.X - this.LeftShoulderEnd.X, this.LeftArmEnd.Y - this.LeftShoulderEnd.Y);
            double rightArmLen = Math.Max(
                this.RightArmEnd.X - this.RightShoulderEnd.X, this.RightArmEnd.Y - this.RightShoulderEnd.Y);
            double branchHeight = neckLength + Math.Max(leftArmLen, rightArmLen);
            
            if (Orientation == System.Windows.Controls.Orientation.Horizontal)
                this.StartPos = new Point(Math.Min(leftArmPos.X, rightArmPos.X) - branchHeight, (leftArmPos.Y+rightArmPos.Y)/2);
            else 
                this.StartPos=new Point((leftArmPos.X+rightArmPos.X)/2, Math.Min(leftArmPos.Y,rightArmPos.Y)-branchHeight);

            double leftShoulderLen = this.StartPos.Y - leftArmPos.Y;
            if(this.Orientation==System.Windows.Controls.Orientation.Vertical)
                leftShoulderLen = this.StartPos.X - leftArmPos.X;
            double rightShoulderLen = rightArmPos.Y - this.StartPos.Y;
            if(this.Orientation==System.Windows.Controls.Orientation.Vertical)
                rightShoulderLen = rightArmPos.X - this.StartPos.X;
            //if (leftIsLeaf)
            {
                leftArmLen = leftArmPos.X - LeftShoulderEnd.X;
                if (this.Orientation == System.Windows.Controls.Orientation.Vertical)
                    leftArmLen = leftArmPos.Y - LeftShoulderEnd.Y;
            }
            //if (rightIsLeaf)
            {
                rightArmLen = rightArmPos.X - LeftShoulderEnd.X;
                if (this.Orientation == System.Windows.Controls.Orientation.Vertical)
                    rightArmLen = rightArmPos.Y - RightShoulderEnd.Y;
            }

            if (this.Orientation == System.Windows.Controls.Orientation.Horizontal)
            {
                this.HeaderPos = new Point() { X = this.StartPos.X + neckLength, Y = this.StartPos.Y };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y - leftShoulderLen };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X + leftArmLen, Y = this.LeftShoulderEnd.Y };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X, Y = this.HeaderPos.Y + rightShoulderLen };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X + rightArmLen, Y = this.RightShoulderEnd.Y };
                this.BoundingBox.SetValue(Canvas.LeftProperty, this.StartPos.X);
                this.BoundingBox.SetValue(Canvas.TopProperty, this.StartPos.Y - leftShoulderLen);
                this.BoundingBox.Width = neckLength;
                this.BoundingBox.Height = leftShoulderLen + rightShoulderLen;
            }
            else
            {
                this.HeaderPos = new Point() { X = this.StartPos.X, Y = this.StartPos.Y + neckLength };
                this.LeftShoulderEnd = new Point() { X = this.HeaderPos.X - leftShoulderLen, Y = this.HeaderPos.Y };
                this.LeftArmEnd = new Point() { X = this.LeftShoulderEnd.X, Y = this.LeftShoulderEnd.Y + leftArmLen };
                this.RightShoulderEnd = new Point() { X = this.HeaderPos.X + rightShoulderLen, Y = this.HeaderPos.Y };
                this.RightArmEnd = new Point() { X = this.RightShoulderEnd.X, Y = this.RightShoulderEnd.Y + rightArmLen };
                this.BoundingBox.SetValue(Canvas.LeftProperty, this.StartPos.X - leftShoulderLen);
                this.BoundingBox.SetValue(Canvas.TopProperty, this.StartPos.Y);
                this.BoundingBox.Width = leftShoulderLen + rightShoulderLen;
                this.BoundingBox.Height = neckLength;
            }

            this.Path.Data = this.DrawBranch();

            if (this.Data.LeftLeaf != null && this.Data.LeftChild == null)
            {
                this._LeftLeafText.SetValue(Canvas.LeftProperty, this.LeftArmEnd.X + 5);
                this._LeftLeafText.SetValue(Canvas.TopProperty, this.LeftArmEnd.Y);
            }
            if (this.Data.RightLeaf != null && this.Data.RightChild == null)
            {
                this._RightLeafText.SetValue(Canvas.LeftProperty, this.RightArmEnd.X + 5);
                this._RightLeafText.SetValue(Canvas.TopProperty, this.RightArmEnd.Y);
            }

            return this.StartPos;
        }
        #endregion
    }
}
